package com.design.oop.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class CommonUtil {
	public static final String BLANK = " ";

	// 디폴트 생성자가 자동으로 생기는 것을 방지한다
	private CommonUtil() {
		throw new AssertionError();
	}

	public static void sleep(long millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void closeQuietly(InputStream stream) {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
	}

	public static void closeQuietly(OutputStream stream) {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
	}

	public static boolean isEmptyList(List<?> list) {
		return list == null || list.isEmpty();
	}

	public static boolean isNotEmptyList(List<?> list) {
		return !isEmptyList(list);
	}

	public static boolean isEmptyMap(Map<?, ?> map) {
		return map == null || map.isEmpty();
	}

	public static boolean isNotEmptyMap(Map<?, ?> map) {
		return !CommonUtil.isEmptyMap(map);
	}

	public static boolean isEmptySet(Set<?> set) {
		return set == null || set.isEmpty();
	}

	public static boolean isNotEmptySet(Set<?> set) {
		return !CommonUtil.isEmptySet(set);
	}

	public static Object defaultIfNull(Object object, Object defaultValue) {
		return object == null ? defaultValue : object;
	}

	public static <T> Set<T> convertListToSet(List<T> list) {
		if (CommonUtil.isEmptyList(list)) {
			return new HashSet<T>();
		}

		return new HashSet<T>(list);
	}

	public static Map<String, Object> sortMap(Map<String, Object> unsortedMap) {
		List<Map.Entry<String, Object>> list = new LinkedList<Map.Entry<String, Object>>(unsortedMap.entrySet());

		Collections.sort(list, new Comparator<Map.Entry<String, Object>>() {
			@Override
			public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
				return o1.getKey().compareTo(o2.getKey());
			}
		});

		Map<String, Object> sortedMap = new LinkedHashMap<String, Object>();
		for (Map.Entry<String, Object> entry : list) {
			sortedMap.put(entry.getKey(), entry.getValue());
		}

		return sortedMap;
	}

	public static boolean isEmpty(Object object) {
		return object == null || "".equals(object);
	}

	/**
	 * Returns an immutable list containing the given elements, in order.
	 *
	 * @throws NullPointerException if any of {@code elements} is null
	 */
	public static <E> ArrayList<E> toArrayList(E... elements) {
		if (elements == null) {
			throw new NullPointerException();
		}

		ArrayList<E> arrayList = new ArrayList<E>();
		switch (elements.length) {
			case 0:
				return arrayList;
			case 1:
				arrayList.add(elements[0]);
				return arrayList;
			default:
				int index = 0;
				for (E element : elements) {
					if (element == null) {
						throw new NullPointerException("at index " + index);
					}
					index++;
					arrayList.add(element);
				}
				return arrayList;
		}
	}

	/**
	 * Generates a hash code for multiple values. The hash code is generated by
	 * calling {@link Arrays#hashCode(Object[])}.
	 *
	 * <p>This is useful for implementing {@link Object#hashCode()}. For example,
	 * in an object that has three properties, {@code x}, {@code y}, and
	 * {@code z}, one could write:
	 * <pre>
	 * public int hashCode() {
	 * 	return Objects.hashCode(getX(), getY(), getZ());
	 * }</pre>
	 *
	 * <b>Warning</b>: When a single object is supplied, the returned hash code
	 * does not equal the hash code of that object.
	 */
	public static int hashCode(Object... objects) {
		return Arrays.hashCode(objects);
	}

	/**
	 * Determines whether two possibly-null objects are equal. Returns:
	 *
	 * <ul>
	 * <li>{@code true} if {@code a} and {@code b} are both null.
	 * <li>{@code true} if {@code a} and {@code b} are both non-null and they are
	 *     equal according to {@link Object#equals(Object)}.
	 * <li>{@code false} in all other situations.
	 * </ul>
	 *
	 * <p>This assumes that any non-null objects passed to this function conform
	 * to the {@code equals()} contract.
	 */
	public static boolean equals(Object objectA, Object objectB) {
		return objectA == objectB || (objectA != null && objectA.equals(objectB));
	}

	/**
	 * Copies an iterable's elements into an array.
	 *
	 * @param iterable the iterable to copy
	 * @param type the type of the elements
	 * @return a newly-allocated array into which all the elements of the iterable
	 *     have been copied
	 */
	public static <T> T[] toArray(Iterable<? extends T> iterable, Class<T> type) {
		Collection<? extends T> collection = (iterable instanceof Collection) ? (Collection<? extends T>)iterable : newArrayList(iterable);
		T[] array = newArray(type, collection.size());
		return collection.toArray(array);
	}

	/**
	 * Creates a <i>mutable</i> {@code ArrayList} instance containing the given
	 * elements.
	 *
	 * @param elements the elements that the list should contain, in order
	 * @return a new {@code ArrayList} containing those elements
	 */
	public static <E> ArrayList<E> newArrayList(Iterable<? extends E> elements) {
		if (elements == null) {
			throw new NullPointerException();
		}

		// Let ArrayList's sizing logic work, if possible
		if (elements instanceof Collection) {
			Collection<? extends E> collection = (Collection<? extends E>)elements;
			return new ArrayList<E>(collection);
		} else {
			return newArrayList(elements.iterator());
		}
	}

	/**
	 * Creates a <i>mutable</i> {@code ArrayList} instance containing the given
	 * elements.
	 *
	 * <p><b>Note:</b> if mutability is not required and the elements are
	 * non-null, use {@link ImmutableList#copyOf(Iterator)} instead.
	 *
	 * @param elements the elements that the list should contain, in order
	 * @return a new {@code ArrayList} containing those elements
	 */
	public static <E> ArrayList<E> newArrayList(Iterator<? extends E> elements) {
		if (elements == null) {
			throw new NullPointerException();
		}
		ArrayList<E> list = new ArrayList<E>();
		while (elements.hasNext()) {
			list.add(elements.next());
		}
		return list;
	}

	/**
	 * Returns a new array of the given length with the specified component type.
	 *
	 * @param type the component type
	 * @param length the length of the new array
	 */
	@SuppressWarnings("unchecked")
	public static <T> T[] newArray(Class<T> type, int length) {
		return (T[])Array.newInstance(type, length);
	}
}